/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Parallel;

import Parallel.Preparser.Preparser;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Stack;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author zwirek
 */
public class Parser extends Thread {

    private static long time0;
    public static int bufferSize=250;
    protected static Parser[] workers;
    protected static ElementHandler[] handlers;
    public static int ended = 0;

    private enum Mode {

        TEXT, ENTITY, OPEN_TAG, CLOSE_TAG, START_TAG, ATTRIBUTE_LVALUE,
        ATTRIBUTE_EQUAL, ATTRIBUTE_RVALUE, QUOTE, IN_TAG, SINGLE_TAG, COMMENT, DONE,
        DOCTYPE, PRE, CDATA
    }
    Reader r;
    private long start, stop;
    public Stack<Node> v;
    private int myNum;
    private Mode mode;
    public boolean end = false;

    public Parser(String filename, int num, ElementHandler[] eh) throws IOException, Exception {
        time0=System.currentTimeMillis();
        workers = new Parser[num];
        File f = new File(filename);
        Preparser pre = new Preparser(filename);
        pre.preparse();
        ArrayList<Integer> preList= pre.nodePositions;
        long l = preList.size() / num;
        workers[0] = new Parser(filename, 0, l - 1, 0);
        for (int i = 0; i < workers.length; i++) {
            int sta = preList.get((int)(i*l));
            int sto = preList.get((int)((i+1)*l)-1);
            workers[i] = new Parser(filename, sta, sto, i);
        }
        handlers = new ElementHandler[eh.length];
        for (int i = 0; i < eh.length; i++) {
            ElementHandler elementHandler = eh[i];
            handlers[i] = elementHandler;
        }
        for (int i = workers.length - 1; i >= 0; i--) {
            workers[i].start();
        }
    }

    private Parser(String filename, long start, long stop, int num) throws IOException, Exception {
        v = new Stack<Node>();
        this.start = start;
        this.stop = stop;
        r = new FileReader(new File(filename));
        r.skip(start);
        myNum = num;
    }

    private synchronized void glue() {
        if (myNum < workers.length - 1) {
            Stack<Node> vv = workers[myNum + 1].v;
            while (!vv.empty() && vv.get(0).isClosed()) {
                Node peek = v.peek();
                if (!peek.isClosed() && !peek.isClosing()) {
                    peek.addNode(vv.get(0));
                    vv.remove(0);
                } else {
                    v.push(vv.get(0));
                    vv.remove(0);
                }
            }
            while (!workers[myNum + 1].end) {
                try {
                    //Thread.sleep(100);
                    wait(1000);
                } catch (InterruptedException ex) {
                    
                }
            }
            while (vv.size() > 0) {
                Node n = vv.get(0);
                if (n.isClosed()) {
                    Node peek = v.peek();
                    if (!peek.isClosed() && !peek.isClosing()) {
                        peek.addNode(n);
                        vv.remove(0);
                    } else {
                        v.push(n);
                        vv.remove(0);
                    }
                } else if (n.isClosing()) {
                    Node nn = v.pop();
                    if (n.getNodes() != null) {
                        nn.addAllNodes(n.getNodes());
                    }
                    nn.addText(n.getText());
                    nn.setClosed(true);
                    vv.remove(0);
                    boolean hold = onEndElement(nn);
                    if(hold){
                        if (!v.empty() && !v.peek().isClosed() && !v.peek().isClosing()) {
                            v.peek().addNode(nn);
                        } else {
                            v.push(nn);
                        }
                    }
                } else {
                    v.peek().addNode(n);
                    vv.remove(0);
                }
            }
            //System.out.println("Size:" + v.size());
            end = true;
            notifyAll();
            if(myNum==0){
                onEndParsing();
                time0 = System.currentTimeMillis()-time0;
                System.out.println("Parsowanie równoległe zajęło: "+time0);
            }
        } else {
            end = true;
            notifyAll();
        }
    }

    @Override
    public void run() {
        try {
//            if (myNum != 0) {
//                preparsing();
//                //System.out.println("Preparsing ended by thread: " + myNum);
//            }
            if(myNum!=0)
                mode=Mode.START_TAG;
            parse();
           // System.out.println("Parsing ended by thread: " + myNum);
            glue();
            //System.out.println("Glue ended by thread: " + myNum);
            ended++;
        } catch (Exception ex) {
            Logger.getLogger(Parser.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void preparsing() throws IOException {
        int c = 0;
        while ((c = r.read()) != -1) {
            if (c != '<') {
                workers[myNum - 1].addStop();
                start++;
            } else {
                mode = Mode.START_TAG;
                return;
            }
        }
    }

    private void addStop() {
        stop++;
    }

    public final Node parse() throws IOException, Exception {
        if (myNum == 0) {
            mode = Mode.PRE;
        }
        int depth = 0;
        int c = 0;
        int quotec = '"';
        StringBuffer sb = new StringBuffer();
        StringBuffer etag = new StringBuffer();
        String tagName = null;
        String lvalue = null;
        String rvalue = null;
        LinkedList<Attribute> attrs = null;
        Stack<Mode> st = new Stack<Mode>();
        if (myNum > 0) {
            st.push(Mode.TEXT);
        }
        //st.push(mode);
        int line = 1, col = 0;
        boolean eol = false;
        while(start!=stop)
        {
            int actualBufferSize = (int) Math.min(bufferSize, stop - start);
            char[] readBuffer = new char[actualBufferSize];
            start += actualBufferSize;
            r.read(readBuffer);
            //        System.out.println(myNum + ": start: " + start + " stop: " + stop);
            for (int i = 0; i < actualBufferSize; i++) {
                c = readBuffer[i];
//            while (((c = r.read()) != -1) && (start != stop)) {//czytamy cały plik znak po znaku
//                start++;
                //            if(myNum!=0)
                //                System.out.println("Read by thread nr: "+myNum+" "+(char)c);
                if (c == '\n' && eol) {//mapujemy znaki nowej linii
                    eol = false;
                    continue;
                } else if (eol) {
                    eol = false;
                } else if (c == '\n') {
                    line++;
                    col = 0;
                } else if (c == '\r') {
                    eol = true;
                    c = '\n';
                    line++;
                    col = 0;
                } else {
                    col++;
                }

                if (mode.equals(Mode.DONE)) {//skończone parsowaniey
                    System.gc();
                    return v.firstElement();
                } else if (mode.equals(Mode.TEXT)) {//jestesmy pomiedzy znacznikami
                    if (c == '<') {
                        st.push(mode);
                        mode = Mode.START_TAG;
                        if (sb.length() > 0) {
                            if (!v.empty()) {
                                v.lastElement().addText(sb.toString());
                                sb.setLength(0);
                            }
                        }
                    } else if (c == '&') {
                        st.push(mode);
                        mode = Mode.ENTITY;
                        etag.setLength(0);
                    } else {
                        sb.append((char) c);
                    }
                } else if (mode.equals(Mode.CLOSE_TAG)) {//wiemy ze czytamy tag zamykający
                    if (c == '>') {
                        mode = popMode(st);
                        tagName = sb.toString();
                        sb.setLength(0);
                        depth--;
                        //                    if (depth == 0) {
                        //                        mode = Mode.DONE;
                        //                    }
                        if (v.isEmpty() || v.peek().isClosing()) {
                            Node n = new Node(tagName);
                            n.setClosing(true);
                            v.push(n);
                        } else if (v.peek().getName().equals(tagName)) {
                            if (v.size() > 1) {
                                Node n = v.pop();
                                n.setClosed(true);
                                Node peek = v.peek();
                                boolean hold = onEndElement(n);
                                if(hold){
                                    if (peek.isClosed() || peek.isClosing()) {
                                        v.push(n);
                                    } else {
                                        peek.addNode(n);
                                    }
                                }
                            } else {
                                Node n = v.peek();
                                n.setClosed(true);
                                boolean hold = onEndElement(n);
                                if(!hold)
                                    v.pop();
                            }
                        } else if (v.peek().isClosed()) {
                            while (v.size() > 0 && v.peek().isClosed()) {
                                Node n = new Node(tagName);
                                n.setClosing(true);
                                n.addNode(v.pop());
                                v.push(n);
                            }
                        } else {
                            //Rzucić exception
                        }
                    } else {
                        sb.append((char) c);
                    }
                } else if (mode.equals(Mode.CDATA)) {//czytamy CDATA
                    if (c == '>'
                            && sb.toString().endsWith("]]")) {
                        sb.setLength(sb.length() - 2);
                        v.peek().addText(sb.toString());
                        sb.setLength(0);
                        mode = popMode(st);
                    } else {
                        sb.append((char) c);
                    }
                } else if (mode.equals(Mode.COMMENT)) {//czytamy komentarz
                    if (c == '>'
                            && sb.toString().endsWith("--")) {
                        sb.setLength(0);
                        mode = popMode(st);
                    } else {
                        sb.append((char) c);
                    }
                } else if (mode.equals(Mode.PRE)) {//nie weszlismy jeszcze do zadnego elementu, nawet roota
                    if (c == '<') {
                        mode = Mode.TEXT;
                        st.push(mode);
                        mode = Mode.START_TAG;
                    }
                } else if (mode.equals(Mode.DOCTYPE)) {//DOCTYPE
                    if (c == '>') {
                        mode = popMode(st);
                        if (mode.equals(Mode.TEXT)) {
                            mode = Mode.PRE;
                        }
                    }
                } else if (mode.equals(Mode.START_TAG)) {//znaleźliśmy znak <
                    mode = popMode(st);
                    if (c == '/') {
                        st.push(mode);
                        mode = Mode.CLOSE_TAG;
                    } else if (c == '?') {
                        mode = Mode.DOCTYPE;
                    } else {
                        st.push(mode);
                        mode = Mode.OPEN_TAG;
                        tagName = null;
                        attrs = new LinkedList<Attribute>();
                        sb.append((char) c);
                    }
                } else if (mode.equals(Mode.ENTITY)) {//encje
                    if (c == ';') {
                        mode = popMode(st);
                        String cent = etag.toString();
                        etag.setLength(0);
                        if (cent.equals("lt")) {
                            sb.append('<');
                        } else if (cent.equals("gt")) {
                            sb.append('>');
                        } else if (cent.equals("amp")) {
                            sb.append('&');
                        } else if (cent.equals("quot")) {
                            sb.append('"');
                        } else if (cent.equals("apos")) {
                            sb.append('\'');
                        } // Could parse hex entities if we wanted to
                        //else if(cent.startsWith("#x"))
                        //sb.append((char)Integer.parseInt(cent.substring(2),16));
                        else if (cent.startsWith("#")) {
                            sb.append((char) Integer.parseInt(cent.substring(1)));
                        } // Insert custom entity definitions here
                        else {
                            exc("Unknown entity: &" + cent + ";", line, col);
                        }
                    } else {
                        etag.append((char) c);
                    }
                } else if (mode.equals(Mode.SINGLE_TAG)) {//pojedyncze tagi jak np <b/
                    if (tagName == null) {
                        tagName = sb.toString();
                    }
                    if (c != '>') {
                        exc("Expected > for tag: <" + tagName + "/>", line, col);
                    }
                    Node n = new Node(tagName);
                    n.setClosed(true);
                    n.addAllAtt(attrs);
                    boolean hold = onEndElement(n);
                    if(hold)
                        v.peek().addNode(n);
                    //                if (depth == 0) {
                    //                    return v.firstElement();
                    //                }
                    sb.setLength(0);
                    attrs = new LinkedList<Attribute>();
                    tagName = null;
                    mode = popMode(st);
                } else if (mode.equals(Mode.OPEN_TAG)) {//jesteśmy po przeczytaniu <
                    if (c == '>') {
                        if (tagName == null) {
                            tagName = sb.toString();
                        }
                        sb.setLength(0);
                        depth++;
                        Node n = new Node(tagName);
                        n.addAllAtt(attrs);
                        v.add(n);
                        onStartElement(n);
                        tagName = null;
                        attrs = new LinkedList<Attribute>();
                        mode = popMode(st);
                    } else if (c == '/') {
                        mode = Mode.SINGLE_TAG;
                    } else if (c == '-' && sb.toString().equals("!-")) {
                        mode = Mode.COMMENT;
                    } else if (c == '[' && sb.toString().equals("![CDATA")) {
                        mode = Mode.CDATA;
                        sb.setLength(0);
                    } else if (c == 'E' && sb.toString().equals("!DOCTYP")) {
                        sb.setLength(0);
                        mode = Mode.DOCTYPE;
                    } else if (Character.isWhitespace((char) c)) {
                        tagName = sb.toString();
                        sb.setLength(0);
                        mode = Mode.IN_TAG;
                    } else {
                        sb.append((char) c);
                    }
                } else if (mode.equals(Mode.QUOTE)) {//prawa strona atrybutu umieszczona w ciapkach
                    if (c == quotec) {
                        rvalue = sb.toString();
                        sb.setLength(0);
                        attrs.add(new Attribute(lvalue, rvalue));
                        mode = Mode.IN_TAG;
                    } else if (" \r\n\u0009".indexOf(c) >= 0) {
                        sb.append(' ');
                    } else if (c == '&') {
                        st.push(mode);
                        mode = Mode.ENTITY;
                        etag.setLength(0);
                    } else {
                        sb.append((char) c);
                    }

                } else if (mode.equals(Mode.ATTRIBUTE_RVALUE)) {//po = w atrybucie
                    if (c == '"' || c == '\'') {
                        quotec = c;
                        mode = Mode.QUOTE;
                    } else if (Character.isWhitespace((char) c)) {
                        ;
                    } else {
                        exc("Error in attribute processing", line, col);
                    }

                } else if (mode.equals(Mode.ATTRIBUTE_LVALUE)) {//lewa strona parametru
                    if (Character.isWhitespace((char) c)) {
                        lvalue = sb.toString();
                        sb.setLength(0);
                        mode = Mode.ATTRIBUTE_EQUAL;
                    } else if (c == '=') {
                        lvalue = sb.toString();
                        sb.setLength(0);
                        mode = Mode.ATTRIBUTE_RVALUE;
                    } else {
                        sb.append((char) c);
                    }

                } else if (mode.equals(Mode.ATTRIBUTE_EQUAL)) {//=
                    if (c == '=') {
                        mode = Mode.ATTRIBUTE_RVALUE;
                    } else if (Character.isWhitespace((char) c)) {

                    } else {
                        exc("Error in attribute processing.", line, col);
                    }

                } else if (mode.equals(Mode.IN_TAG)) {//jesteśmy w środku znacznika < . >
                    if (c == '>') {
                        mode = popMode(st);
                        Node n = new Node(tagName);
                        n.addAllAtt(attrs);
                        v.add(n);
                        depth++;
                        tagName = null;
                        attrs = new LinkedList<Attribute>();
                    } else if (c == '/') {
                        mode = Mode.SINGLE_TAG;
                    } else if (Character.isWhitespace((char) c)) {
                    } else {
                        mode = Mode.ATTRIBUTE_LVALUE;
                        sb.append((char) c);
                    }
                }
            }
        }

//        if (mode.equals(Mode.DONE))//przetworzone wszystko
//        {
//            return v.firstElement();
//        } else {
//            //exc("missing end tag", line, col);
//        }
        System.gc();
        return v.firstElement();
    }

    private static Mode popMode(Stack<Mode> st) {
        if (!st.empty()) {
            return st.pop();
        } else {
            return Mode.PRE;
        }
    }

    private static void exc(String s, int line, int col)//wyjątek
            throws Exception {
        throw new Exception(s + " near line " + line + ", column " + col);
    }

    protected void onStartElement(Node n){
        handlers[myNum].onStartElement(n);
    }

    protected boolean onEndElement(Node n){
        return handlers[myNum].onEndElement(n);
    }

    protected void onEndParsing(){
        handlers[myNum].onEndParsing(this);
    }
}
